#ifndef QEXPANDOPENGLWIDGET_H
#define QEXPANDOPENGLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLExtraFunctions>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/ext.hpp>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <QDebug>
#include <QMouseEvent>
#include <QPoint>
class QExpandOpenGLWidget: public QOpenGLWidget, protected QOpenGLExtraFunctions
{
public:
    class Shader {
    public:
        unsigned int shaderProgram;
        QExpandOpenGLWidget* m_parent = nullptr;
        Shader(QExpandOpenGLWidget* parent): m_parent(parent) {}

        // 读取shader配置字符串
        void readShaderStr(const char* vertexStr, const char* fragmentStr){
            GLuint vertexShader = m_parent->glCreateShader(GL_VERTEX_SHADER);
            m_parent->glShaderSource(vertexShader, 1, &vertexStr, NULL);
            m_parent->glCompileShader(vertexShader);

            int success;
            char infoLog[512];
            m_parent->glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                m_parent->glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
                qDebug() << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog;
            }
            GLuint fragmentShader = m_parent->glCreateShader(GL_FRAGMENT_SHADER);

            m_parent->glShaderSource(fragmentShader, 1, &fragmentStr, NULL);
            m_parent->glCompileShader(fragmentShader);

            m_parent->glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
            if (!success)
            {
                m_parent->glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
                qDebug() << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog;
            }
            shaderProgram = m_parent->glCreateProgram();
            m_parent->glAttachShader(shaderProgram, vertexShader);
            m_parent->glAttachShader(shaderProgram, fragmentShader);
            m_parent->glLinkProgram(shaderProgram);

            m_parent->glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
            if (!success) {
                m_parent->glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
                qDebug() << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog;
            }
            m_parent->glDeleteShader(vertexShader);
            m_parent->glDeleteShader(fragmentShader);

        }
        // utility function for checking shader compilation/linking errors.
        // ------------------------------------------------------------------------
        void checkCompileErrors(GLuint shader, std::string type)
        {
            GLint success;
            GLchar infoLog[1024];
            if(type != "PROGRAM")
            {
                m_parent->glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
                if(!success)

                {
                    m_parent->glGetShaderInfoLog(shader, 1024, NULL, infoLog);
                    std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
                }
            }
            else
            {
                m_parent->glGetProgramiv(shader, GL_LINK_STATUS, &success);
                if(!success)
                {
                    m_parent->glGetProgramInfoLog(shader, 1024, NULL, infoLog);
                    std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;
                }
            }
        }

        // activate the shader
        // ------------------------------------------------------------------------
        void use()
        {
            m_parent->glUseProgram(shaderProgram);
        }

        // utility uniform functions
        // ------------------------------------------------------------------------
        void setBool(const std::string &name, bool value)
        {
            m_parent->glUniform1i(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), (int)value);
        }

        // ------------------------------------------------------------------------
        void setInt(const std::string &name, int value)
        {
            m_parent->glUniform1i(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), value);
        }

        // ------------------------------------------------------------------------
        void setFloat(const std::string &name, float value)
        {
            m_parent->glUniform1f(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), value);
        }

        // ------------------------------------------------------------------------
        void setVec2(const std::string &name, const glm::vec2 &value)
        {
            m_parent->glUniform2fv(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), 1, &value[0]);
        }

        void setVec2(const std::string &name, float x, float y)
        {
            m_parent->glUniform2f(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), x, y);
        }

        // ------------------------------------------------------------------------
        void setVec3(const std::string &name, const glm::vec3 &value)
        {
            m_parent->glUniform3fv(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), 1, &value[0]);
        }

        void setVec3(const std::string &name, float x, float y, float z)
        {
            m_parent->glUniform3f(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), x, y, z);
        }

        // ------------------------------------------------------------------------
        void setVec4(const std::string &name, const glm::vec4 &value)
        {
            m_parent->glUniform4fv(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), 1, &value[0]);
        }

        void setVec4(const std::string &name, float x, float y, float z, float w)
        {
            m_parent->glUniform4f(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), x, y, z, w);
        }

        // ------------------------------------------------------------------------
        void setMat2(const std::string &name, const glm::mat2 &mat)
        {
            m_parent->glUniformMatrix2fv(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), 1, GL_FALSE, &mat[0][0]);
        }

        // ------------------------------------------------------------------------
        void setMat3(const std::string &name, const glm::mat3 &mat)
        {
            m_parent->glUniformMatrix3fv(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), 1, GL_FALSE, &mat[0][0]);
        }

        // ------------------------------------------------------------------------
        void setMat4(const std::string &name, const glm::mat4 &mat)
        {
            m_parent->glUniformMatrix4fv(m_parent->glGetUniformLocation(shaderProgram, name.c_str()), 1, GL_FALSE, &mat[0][0]);
        }
    };

protected:
    // 相机位置
    glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f,  6.0f);
    // 相机前进向量
    glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
    // 相机上向量
    glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f,  0.0f);
    // 投影矩阵
    glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)this->frameGeometry().width() / this->frameGeometry().height(), 0.1f, 100.0f);
    // 观察矩阵
    glm::mat4 view = glm::mat4(1.0f);
    // 上一次鼠标点击的位置
    QPoint m_point;
    // 是否是鼠标第一次点击
    bool m_firstClick = true;
    // 鼠标左键是否按下
    bool m_leftMouseButton = false;
    // 偏移角度,俯仰角，偏航角，滑轮偏移
    float m_pitch = 0.0f, m_yaw = -90.0f, m_fov= 45.0f;
public:
    QExpandOpenGLWidget(){};

    // 鼠标按下事件
    void mousePressEvent(QMouseEvent *event){
        if(event->button() == Qt::LeftButton)
        {
            m_point = event->pos();
            m_leftMouseButton = true;
            if(m_firstClick){
                m_firstClick = false;
            }
        }
    }

    // 鼠标松开事件
    void mouseReleaseEvent(QMouseEvent* event){
        if (event->button() == Qt::LeftButton) {
            m_leftMouseButton = false;
        }
        event->ignore();
    }

    void mouseMoveEvent(QMouseEvent* event){
        if (m_leftMouseButton&&!m_firstClick)
        {
            float xoffset = event->x() - m_point.x();
            float yoffset = event->y() - m_point.y(); // 注意这里是相反的，因为y坐标是从底部往顶部依次增大的

            float sensitivity = 0.05f;
            xoffset *= sensitivity;
            yoffset *= sensitivity;
            m_yaw   -= xoffset;
            m_pitch += yoffset;
            if(m_pitch > 89.0f)
                m_pitch =  89.0f;
            if(m_pitch < -89.0f)
                m_pitch = -89.0f;
            glm::vec3 front;
            front.x = cos(glm::radians(m_pitch)) * cos(glm::radians(m_yaw));
            front.y = sin(glm::radians(m_pitch));
            front.z = cos(glm::radians(m_pitch)) * sin(glm::radians(m_yaw));
            cameraFront = glm::normalize(front);
        }
        m_point = event->pos();
        event->ignore();
    }

    // 滑轮事件
    void wheelEvent(QWheelEvent *event){
        QPoint numDegrees = event->angleDelta();
        m_fov -= numDegrees.y()/100;                // 进行放大
        qDebug() << m_fov;
        if(m_fov <= 1.0f)
            m_fov = 1.0f;
        if(m_fov >= 45.0f)
            m_fov = 45.0f;
        projection = glm::perspective(glm::radians(m_fov), (float)this->frameGeometry().width() / this->frameGeometry().height(), 0.1f, 100.0f);
    }

    //键盘按下事件
    void keyPressEvent(QKeyEvent *event) {
        float cameraSpeed = 0.2f;
        if(event->key() == Qt::Key_Left){
            cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
        }else if(event->key() == Qt::Key_Right){
            cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
        }else if(event->key() == Qt::Key_Up){
            cameraPos += cameraSpeed * cameraFront;
        }else if(event->key() == Qt::Key_Down){
            cameraPos -= cameraSpeed * cameraFront;
        }
        view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
    }

    void resizeGL(int w, int h){
        glViewport(0, 0, w, h);
        projection = glm::perspective(glm::radians(m_fov), (float)this->frameGeometry().width() / this->frameGeometry().height(), 0.1f, 100.0f);
    }

};

#endif // QEXPANDOPENGLWIDGET_H
